package com.massyframework.beanskin.maven.plugin;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.text.ParseException;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.massyframework.beanskin.maven.plugin.util.MapVariableReplacer;
import com.massyframework.beanskin.maven.plugin.util.StringVariableReplacer;

/**
 * 模块xml写入器
 * @author huangkh
 *
 */
public class ModuleXmlWriter {
	
	// 模块属性
	private static final String GROUPID        = "groupId";
	private static final String MODULEID       = "moduleId";
	private static final String VERSION        = "version";
	
	// 解密
	private static final String SECURE         = "secure";
	private static final String PUBLICKEY      = "publicKey";
	
	private static final String X509CERT       = "x509cert";
	private static final String ENCRYPTED      = "encrypted";
	private static final String SCRIPTS        = "scripts";
	private static final String CLASSES        = "classes";
	private static final String NAME           = "name";
	
	
	/**
	 * 构造方法
	 */
	public ModuleXmlWriter() {
	}
	
	/**
	 * 复制并替换
	 * 
	 * @param inputFile {@link File},输入文件
	 * @param outputFile {@link File},输出文件
	 * @param module {@link Module},模块
	 * @param certificate {@link Certificate},证书
	 * @param pubKeyStr {@link String}
	 * @param artifacts {@link Collection}
	 * @throws IOException 例外
	 */
	public void copyAndReplace(File inputFile, File outputFile, Module module, Certificate certificate, 
			String pubKeyStr, Collection<EncryptableArtifact> artifacts) throws Exception{
		//文档
		Document doc = this.loadDocument(inputFile);
		
		//模块节点
		Node node = this.foundSingleNodeByTagName(module.getType(), doc);
		//替换模块变量
		this.replaceModuleVariables(node, module, doc);
		
		//替换加密节点
		this.replaceEncryptClasses(node, certificate, pubKeyStr, artifacts, doc);
		
		//输出
		this.writeDocument(doc, outputFile);
	}
	
	protected void writeDocument(Document doc, File outputFile) throws TransformerException, IOException {
		TransformerFactory transformerFactory = TransformerFactory.newInstance();
		Transformer transformer = transformerFactory.newTransformer();
		DOMSource source = new DOMSource(doc);

		FileWriter writer = null;
		try {
			writer =new FileWriter(outputFile);
			StreamResult result = new StreamResult(writer);
			transformer.setOutputProperty(OutputKeys.INDENT, "yes");
			transformer.setOutputProperty(
					"{http://xml.apache.org/xslt}indent-amount", "2");
			transformer.transform(source, result);
		}finally {
			if (writer != null) {
				try {
					writer.close();
				}catch(Exception e) {
					
				}
			}
		}
	}
	
	/**
	 * 添加加密类
	 * @param moduleNode {@link Node}, 模块节点
	 * @param pubKeyStr {@link String}, 公钥
	 * @param artifacts {@link Collection} artifacts
	 * @param doc {@link Document}
	 */
	protected void replaceEncryptClasses(Node moduleNode, Certificate certificate, String pubKeyStr, 
			Collection<EncryptableArtifact> artifacts, Document doc) {
		Node secureNode = this.foundNodeByList(moduleNode.getChildNodes(), SECURE);
		if (secureNode != null) {
			this.deleteChrendNodes(secureNode);
		}
		
		if (!artifacts.isEmpty() && pubKeyStr != null) {
			if (secureNode == null) {
				Element element = doc.createElement(SECURE);
				secureNode = (Node)element;
				moduleNode.appendChild(secureNode);
			}
			
			Element element = doc.createElement(PUBLICKEY);
			Node pubKeyNode = (Node)element;
			secureNode.appendChild(pubKeyNode);
			pubKeyNode.setTextContent(pubKeyStr);
			
			if (certificate != null) {
				element = doc.createElement(X509CERT);
				Node certNode = (Node)element;
				secureNode.appendChild(certNode);
				
				element = doc.createElement(NAME);
				Node node = (Node)element;
				certNode.appendChild(node);
				node.setTextContent(certificate.getName());
				
				element = doc.createElement(ENCRYPTED);
				node = (Node)element;
				certNode.appendChild(node);
				node.setTextContent(certificate.getMd5());
			}
			
			
			element = doc.createElement(CLASSES);
			Node classesNode = (Node)element;
			secureNode.appendChild(classesNode);
						
			for (EncryptableArtifact artifact: artifacts) {
				List<String> classNames = artifact.getClassNames();
				for (String className: classNames) {
					element = doc.createElement(NAME);
					Node classNode = (Node)element;
					classNode.setTextContent(className);
					classesNode.appendChild(classNode);
				}
			}
			
			element = doc.createElement(SCRIPTS);
			Node scriptNode = (Node)element;
			secureNode.appendChild(scriptNode);
			for (EncryptableArtifact artifact: artifacts) {
				List<String> fileNames = artifact.getFileNames();
				for (String fileName: fileNames) {
					element = doc.createElement(NAME);
					Node fileNode = (Node)element;
					fileNode.setTextContent(fileName);
					scriptNode.appendChild(fileNode);
				}
			}
			
			
		}else {
			if (secureNode != null) {
				Element parent = (Element)moduleNode;
				parent.removeChild(secureNode);
			}
		}
	}
	
	/**
	 * 替换模块变量
	 * @param nodes {@link NodeList}
	 * @param module {@link Module}
	 */
	protected void replaceModuleVariables(Node moduleNode, Module module, Document doc) {
		Node gruoupIdNode = this.foundNodeByList(moduleNode.getChildNodes(), GROUPID);
		if (gruoupIdNode == null) {
			Element element = doc.createElement(GROUPID);
			gruoupIdNode = (Node)element;
			moduleNode.appendChild(gruoupIdNode);
		}
		gruoupIdNode.setTextContent(module.getGroupId());
	
		Node moduleIdNode = this.foundNodeByList(moduleNode.getChildNodes(), MODULEID);
		if (moduleIdNode == null) {
			Element element = doc.createElement(MODULEID);
			moduleIdNode = (Node)element;
			moduleNode.appendChild(moduleIdNode);
		}
		moduleIdNode.setTextContent(module.getModuleId());
		
		Node versionNode = this.foundNodeByList(moduleNode.getChildNodes(), VERSION);
		if (versionNode == null) {
			Element element = doc.createElement(VERSION);
			versionNode = (Node)element;
			moduleNode.appendChild(versionNode);
		}
		versionNode.setTextContent(module.getVersion());
	}
	
		
	/**
	 * 替换
	 * @param identifier
	 * @throws IOException 
	 */
	public void doReplace(Module module, File outputFile) throws IOException {
		StringVariableReplacer replacer = new MapVariableReplacer();
		Map<String, String> variables = new HashMap<>();
		variables.put("groupId", module.getGroupId());
		variables.put("moduleId", module.getModuleId());
		variables.put("version", module.getVersion());
		replacer.addVariables(variables);
		
		StringBuilder builder = new StringBuilder();
		InputStream inputStream = null;
		BufferedReader reader= null;
		try {
			inputStream = new FileInputStream(outputFile);
			reader = new BufferedReader(new InputStreamReader(inputStream));
			
			String line = null;
			while ((line = reader.readLine()) != null) {
				builder.append(line).append("\r\n");
			}
		}finally{
			if (reader != null) {
				try {
					reader.close();
				}catch(Exception e) {
					
				}
			}
			
			if (inputStream != null) {
				try {
					inputStream.close();
				}catch(Exception e) {
					
				}
			}
		}
		
		//替换
		String text = builder.toString();
		
		
		text = replacer.foundAndReplace(text);
		
		File parent = outputFile.getParentFile();
		if (!parent.exists()) {
			outputFile.getParentFile().mkdirs();
		}
		
		//重新写入
		if(outputFile.exists()){
			outputFile.delete();
		}
		
		outputFile.createNewFile();
		OutputStream outputStream = null;
		BufferedWriter writer = null;
		try {
			outputStream = new FileOutputStream(outputFile);
			writer = new BufferedWriter(new OutputStreamWriter(outputStream, "utf-8"));
            writer.write(text);
		}finally {
			if (writer != null) {
				try {
					writer.close();
				}catch(Exception e) {
					
				}
			}
			
			if (outputStream != null) {
				try {
					outputStream.close();
				}catch(Exception e) {
					
				}
			}
		}
	}
	
	/**
	 * 删除子节点
	 * @param node {@link Node}
	 */
	protected void deleteChrendNodes(Node node) {
		if (node == null) return;
		
		Element parent = (Element)node;
		NodeList nodes = node.getChildNodes();
		int size = nodes.getLength();
		
		for (int i=size-1; i>=0; i--) {
			Node child = nodes.item(i);
			if (child.hasChildNodes()) {
				this.deleteChrendNodes(node);
			}
			
			parent.removeChild(child);
		}
	}
	
	/**
	 * 按名称查找单一节点
	 * @param name {@link String},名称
	 * @param doc {@link Document},文档
	 * @return {@link Node}
	 */
	protected Node foundSingleNodeByTagName(String name, Document doc) {
		NodeList nodes = doc.getElementsByTagName(name);
		return nodes.item(0);
	}
	
	/**
	 * 按{@code name}在{@code nodes}查找节点
	 * @param nodes {@link NodeList}
	 * @param name {@link String},名称
	 * @return {@link Node}
	 */
	protected Node foundNodeByList(NodeList nodes, String name) {
		int size = nodes.getLength();
		for (int i=0; i<size; i++) {
			Node node = nodes.item(i);
			if (node.getNodeName().equals(name)) {
				return node;
			}
		}
		return null;
	}
	
	/**
	 * 从{@code inputFile}加载xml doc
	 * @param inputFile {@link File},输入文件
	 * @return {@link Document}
	 */
	protected Document loadDocument(File inputFile) throws Exception {
		InputStream inputStream = null;
		try {
			inputStream = new FileInputStream(inputFile);
			Document doc = this.createDocument(inputStream);
			return doc;
		}finally {
			if (inputStream != null) {
				try {
					inputStream.close();
				}catch(Exception e) {
					
				}
			}
		}
	}
		
	/**
	 * 创建Xml Document对象
	 * 
	 * @param inputStream 输入流
	 * @return {@link Document}
	 * @throws ParseException 解析发生错误抛出的异常
	 * @throws IOException    IO读写发生异常抛出的异常
	 * @throws SAXException   SAX解析发生错误抛出的异常
	 */
	protected final Document createDocument(InputStream inputStream) 
			throws Exception, IOException {
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		DocumentBuilder builder = factory.newDocumentBuilder();
		return builder.parse(inputStream);
	}
}
